package net.neptech.service.impl;

import io.minio.*;
import net.neptech.service.FileService;
import net.neptech.service.RedisService;
import net.neptech.utils.UploadContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.http.Method;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Value;
@Service
public class FileServiceImpl implements FileService {

    private final MinioClient minioClient;
    private final RedisService redisService;

    @Value("${minio.bucket-name}")
    private String bucketName;

    @Value("${minio.chunk-size:5242880}")
    private Long chunkSize;

    @Value("${minio.endpoint}")
    private String endpoint;

    @Autowired
    public FileServiceImpl(MinioClient minioClient, RedisService redisService) {
        this.minioClient = minioClient;
        this.redisService = redisService;
    }

    // 普通大文件上传：使用自动分片
    @Override
    public void uploadFile(MultipartFile file) throws Exception {
        try (InputStream inputStream = file.getInputStream()) {
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(file.getOriginalFilename())
                    .stream(inputStream, file.getSize(), -1) // -1 表示自动选择 partSize
                    .contentType(file.getContentType())
                    .build();
            minioClient.putObject(args);
        } catch (Exception e) {
            throw new RuntimeException("文件上传失败", e);
        }
    }

    // 初始化上传（不再初始化 Multipart，而是准备分片上传上下文）
    @Override
    public UploadContext initUpload(String filename, long fileSize, int totalChunks) throws Exception {
        String fileId = UUID.randomUUID().toString();
        String finalObjectName = "uploads/" + filename;
        UploadContext context = new UploadContext(fileId, filename, finalObjectName, fileSize, totalChunks);
        redisService.setCacheObject(fileId, context);
        return context;
    }

    //上传单个分片：每个分片作为独立对象上传
    @Override
    public boolean uploadChunk(String fileId, int chunkNumber, MultipartFile chunk) throws Exception {
        UploadContext context =redisService.getCacheObject(fileId);
        if (context == null || context.isExpired()) {
            throw new IllegalStateException("上传会话不存在或已过期");
        }

        // 分片对象名：chunks/{fileId}/part.00001
        String chunkObjectName = context.getUploadDir() + "/part." + String.format("%05d", chunkNumber);

        try (InputStream inputStream = chunk.getInputStream()) {
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(chunkObjectName)
                    .stream(inputStream, chunk.getSize(), -1)
                    .contentType("application/octet-stream")
                    .build();

            minioClient.putObject(args);

            // 记录已上传的分片号
            context.getUploadedChunks().add(chunkNumber);
            redisService.setCacheObject(fileId, context);

            return true;
        } catch (Exception e) {
            throw new RuntimeException("分片上传失败: part-" + chunkNumber, e);
        }
    }

    // ✅ 合并所有分片：使用 composeObject
    @Override
    public String completeUpload(String fileId) throws Exception {
        UploadContext context = redisService.getCacheObject(fileId);
        if (context == null) {
            throw new IllegalStateException("上传会话不存在");
        }

        if (context.getUploadedChunks().size() < context.getTotalChunks()) {
            throw new IllegalStateException("还有分片未上传完成");
        }

        // 构建 ComposeSource 列表（按分片编号排序）
        List<ComposeSource> sources = new ArrayList<>();
        for (int i = 1; i <= context.getTotalChunks(); i++) {
            String chunkObjectName = context.getUploadDir() + "/part." + String.format("%05d", i);
            sources.add(
                    ComposeSource.builder()
                            .bucket(bucketName)
                            .object(chunkObjectName)
                            .build()
            );
        }

        // 合并到最终对象
        minioClient.composeObject(
                ComposeObjectArgs.builder()
                        .bucket(bucketName)
                        .object(context.getObjectName()) // 最终文件名
                        .sources(sources)
                        .build()
        );

        // ✅ 合并成功后删除所有临时分片
        for (ComposeSource source : sources) {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(source.object())
                            .build()
            );
        }
            redisService.deleteObject(fileId);
        return context.getObjectName(); // 返回可访问的路径
    }

    //取消上传：删除所有已上传的分片（如果有）
    @Override
    public void abortUpload(String fileId) throws Exception {
        UploadContext context =redisService.getCacheObject(fileId);
        if (context == null) {
            return;
        }

        try {
            // 删除所有已上传的分片对象
            for (Integer chunkNumber : context.getUploadedChunks()) {
                String chunkObjectName = context.getUploadDir() + "/part." + String.format("%05d", chunkNumber);
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(chunkObjectName)
                                .build()
                );
            }
        } catch (Exception ignore) {
        } finally {
            redisService.deleteObject(fileId);
        }
    }

    /**
     * 会议里面的文件上传
     */
    @Override
    public String uploadFileInMeeting(MultipartFile file,String userName,String conferenceId) throws Exception{
        try {
            String originalFilename = file.getOriginalFilename();
            System.out.println("原始文件名是"+originalFilename);
            // 1. 解析原始文件名，分离文件名和扩展名
            String baseName;
            String extension = "";
            int lastDotIndex = originalFilename.lastIndexOf('.');
            if (lastDotIndex > 0) {
                baseName = originalFilename.substring(0, lastDotIndex);
                extension = originalFilename.substring(lastDotIndex);
            } else {
                baseName = originalFilename;
                extension = "";
            }
            // 2. 生成日期字符串：yyyyMMdd 格式
            String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            // 3. 清理用户名，避免文件系统非法字符（如 / \ : 等）
            String safeUserName = userName.replaceAll("[^\\w\\u4e00-\\u9fa5]", "_");
            // 4. 构造新文件名：原始名_姓名_日期.扩展名
            String uniqueFileName = baseName + "_" + safeUserName + "_" + dateStr + extension;
            // 5. 可选：放在特定目录下，如 meeting-files/
            String objectName = "meeting-files/" + conferenceId + "/" + uniqueFileName;
            // 6. 执行上传（使用 MinIO 客户端）
            String downloadFileName = baseName + "_" + safeUserName + "_(" + dateStr + ")" + extension;
            // 上传时尽量写一个合理的 Content-Type（元数据），防止 MinIO 走默认 octet-stream
                    String contentType = guessContentType(originalFilename, file.getContentType());
                    try (InputStream inputStream = file.getInputStream()) {
                        PutObjectArgs args = PutObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .stream(inputStream, file.getSize(), -1)
                                .contentType(contentType)
                                .build();
                        minioClient.putObject(args);
                    }

             // 关键：返回预签名直链，强制 attachment 下载 + UTF-8 文件名
                    String url = presignedDownloadUrl(objectName, downloadFileName, contentType);
                    System.out.println("✅文件上传返回的下载地址为：" + url);
                    return url;
        } catch (Exception e) {
            throw new RuntimeException("会议文件上传失败", e);
        }
    }

    private String guessContentType(String filename, String fallback) {
        try {
            String probed = Files.probeContentType(Paths.get(filename));
            if (probed != null) return probed;
        } catch (Exception ignored) {}
        // 常见类型兜底
        String lower = filename.toLowerCase();
        if (lower.endsWith(".md")) return "text/markdown; charset=UTF-8";
        if (lower.endsWith(".txt")) return "text/plain; charset=UTF-8";
        if (lower.endsWith(".pdf")) return "application/pdf";
        if (lower.endsWith(".png")) return "image/png";
        if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
        return fallback != null ? fallback : "application/octet-stream";
    }

    // 生成带下载文件名的预签名URL
    private String presignedDownloadUrl(String objectName, String downloadFileName, String contentType) {
        try {
            // RFC5987：filename* 支持 UTF-8
            String filenameStar = "UTF-8''" + URLEncoder.encode(downloadFileName, StandardCharsets.UTF_8);
            // 旧浏览器兜底的 filename（会乱码但无伤大雅）
            String filenameFallback = new String(downloadFileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);

            Map<String, String> qp = new HashMap<>();
            qp.put("response-content-disposition",
                    "attachment; filename=\"" + filenameFallback + "\"; filename*=" + filenameStar);
            qp.put("response-content-type", contentType);

            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(1, TimeUnit.HOURS) // 链接有效期
                            .extraQueryParams(qp)
                            .build()
            );
        } catch (Exception e) {
            throw new RuntimeException("生成预签名下载链接失败", e);
        }
    }

    /**
     * minio在上传文件后会自动返回一个下载链接，可以隐藏到某一个按钮里面，这样子点击就能下载了
     */
    private String getFileUrl(String objectName) {
               try {
                       // ✅ 返回预签名直链（点就能下，不需要改 MinIO 策略）
                               return minioClient.getPresignedObjectUrl(
                                       GetPresignedObjectUrlArgs.builder()
                                                       .method(Method.GET)
                                               .bucket(bucketName)
                                               .object(objectName)
                                               .expiry(60 * 60) // 一小时有效
                                               .build()
                               );
                   } catch (Exception e) {
                       throw new RuntimeException("生成下载链接失败", e);
                   }
           }
    }